package {

    /**
    *   String Utilities class by Ryan Matsikas, Feb 10 2006
    *
    *   Visit www.gskinner.com for documentation, updates and more free code.
    *   You may distribute this code freely, as long as this comment block remains intact.
    */

    public class StringUtils {

        /**
        *   Returns everything after the first occurrence of the provided character in the string.
        *
        *   @param p_string The string.
        *
        *   @param p_begin The character or sub-string.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function afterFirst(p_string:String, p_char:String):String {
            if (p_string == null) { return ''; }
            var idx:int = p_string.indexOf(p_char);
            if (idx == -1) { return ''; }
            idx += p_char.length;
            return p_string.substr(idx);
        }

        /**
        *   Returns everything after the last occurence of the provided character in p_string.
        *
        *   @param p_string The string.
        *
        *   @param p_char The character or sub-string.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function afterLast(p_string:String, p_char:String):String {
            if (p_string == null) { return ''; }
            var idx:int = p_string.lastIndexOf(p_char);
            if (idx == -1) { return ''; }
            idx += p_char.length;
            return p_string.substr(idx);
        }

        /**
        *   Determines whether the specified string begins with the specified prefix.
        *
        *   @param p_string The string that the prefix will be checked against.
        *
        *   @param p_begin The prefix that will be tested against the string.
        *
        *   @returns Boolean
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function beginsWith(p_string:String, p_begin:String):Boolean {
            if (p_string == null) { return false; }
            return p_string.indexOf(p_begin) == 0;
        }

        /**
        *   Returns everything before the first occurrence of the provided character in the string.
        *
        *   @param p_string The string.
        *
        *   @param p_begin The character or sub-string.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function beforeFirst(p_string:String, p_char:String):String {
            if (p_string == null) { return ''; }
            var idx:int = p_string.indexOf(p_char);
            if (idx == -1) { return ''; }
            return p_string.substr(0, idx);
        }

        /**
        *   Returns everything before the last occurrence of the provided character in the string.
        *
        *   @param p_string The string.
        *
        *   @param p_begin The character or sub-string.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function beforeLast(p_string:String, p_char:String):String {
            if (p_string == null) { return ''; }
            var idx:int = p_string.lastIndexOf(p_char);
            if (idx == -1) { return ''; }
            return p_string.substr(0, idx);
        }

        /**
        *   Returns everything after the first occurance of p_start and before
        *   the first occurrence of p_end in p_string.
        *
        *   @param p_string The string.
        *
        *   @param p_start The character or sub-string to use as the start index.
        *
        *   @param p_end The character or sub-string to use as the end index.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function between(p_string:String, p_start:String, p_end:String):String {
            var str:String = '';
            if (p_string == null) { return str; }
            var startIdx:int = p_string.indexOf(p_start);
            if (startIdx != -1) {
                startIdx += p_start.length; // RM: should we support multiple chars? (or ++startIdx);
                var endIdx:int = p_string.indexOf(p_end, startIdx);
                if (endIdx != -1) { str = p_string.substr(startIdx, endIdx-startIdx); }
            }
            return str;
        }

        /**
        *   Description, Utility method that intelligently breaks up your string,
        *   allowing you to create blocks of readable text.
        *   This method returns you the closest possible match to the p_delim paramater,
        *   while keeping the text length within the p_len paramter.
        *   If a match can't be found in your specified length an  '...' is added to that block,
        *   and the blocking continues untill all the text is broken apart.
        *
        *   @param p_string The string to break up.
        *
        *   @param p_len Maximum length of each block of text.
        *
        *   @param p_delim delimter to end text blocks on, default = '.'
        *
        *   @returns Array
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function block(p_string:String, p_len:uint, p_delim:String = "."):Array {
            var arr:Array = new Array();
            if (p_string == null || !contains(p_string, p_delim)) { return arr; }
            var chrIndex:uint = 0;
            var strLen:uint = p_string.length;
            var replPatt:RegExp = new RegExp("[^"+escapePattern(p_delim)+"]+$");
            while (chrIndex <  strLen) {
                var subString:String = p_string.substr(chrIndex, p_len);
                if (!contains(subString, p_delim)){
                    arr.push(truncate(subString, subString.length));
                    chrIndex += subString.length;
                }
                subString = subString.replace(replPatt, '');
                arr.push(subString);
                chrIndex += subString.length;
            }
            return arr;
        }

        /**
        *   Capitallizes the first word in a string or all words..
        *
        *   @param p_string The string.
        *
        *   @param p_all (optional) Boolean value indicating if we should
        *   capitalize all words or only the first.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function capitalize(p_string:String, ...args):String {
            var str:String = trimLeft(p_string);
            trace('capl', args[0])
            if (args[0] === true) { return str.replace(/^.|\b./g, _upperCase);}
            else { return str.replace(/(^\w)/, _upperCase); }
        }

        /**
        *   Determines whether the specified string contains any instances of p_char.
        *
        *   @param p_string The string.
        *
        *   @param p_char The character or sub-string we are looking for.
        *
        *   @returns Boolean
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function contains(p_string:String, p_char:String):Boolean {
            if (p_string == null) { return false; }
            return p_string.indexOf(p_char) != -1;
        }

        /**
        *   Determines the number of times a charactor or sub-string appears within the string.
        *
        *   @param p_string The string.
        *
        *   @param p_char The character or sub-string to count.
        *
        *   @param p_caseSensitive (optional, default is true) A boolean flag to indicate if the
        *   search is case sensitive.
        *
        *   @returns uint
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function countOf(p_string:String, p_char:String, p_caseSensitive:Boolean = true):uint {
            if (p_string == null) { return 0; }
            var char:String = escapePattern(p_char);
            var flags:String = (!p_caseSensitive) ? 'ig' : 'g';
            return p_string.match(new RegExp(char, flags)).length;
        }

        /**
        *   Levenshtein distance (editDistance) is a measure of the similarity between two strings,
        *   The distance is the number of deletions, insertions, or substitutions required to
        *   transform p_source into p_target.
        *
        *   @param p_source The source string.
        *
        *   @param p_target The target string.
        *
        *   @returns uint
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function editDistance(p_source:String, p_target:String):uint {
            var i:uint;

            if (p_source == null) { p_source = ''; }
            if (p_target == null) { p_target = ''; }

            if (p_source == p_target) { return 0; }

            var d:Array = new Array();
            var cost:uint;
            var n:uint = p_source.length;
            var m:uint = p_target.length;
            var j:uint;

            if (n == 0) { return m; }
            if (m == 0) { return n; }

            for (i=0; i<=n; i++) { d[i] = new Array(); }
            for (i=0; i<=n; i++) { d[i][0] = i; }
            for (j=0; j<=m; j++) { d[0][j] = j; }

            for (i=1; i<=n; i++) {

                var s_i:String = p_source.charAt(i-1);
                for (j=1; j<=m; j++) {

                    var t_j:String = p_target.charAt(j-1);

                    if (s_i == t_j) { cost = 0; }
                    else { cost = 1; }

                    d[i][j] = _minimum(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+cost);
                }
            }
            return d[n][m];
        }

        /**
        *   Determines whether the specified string ends with the specified suffix.
        *
        *   @param p_string The string that the suffic will be checked against.
        *
        *   @param p_end The suffix that will be tested against the string.
        *
        *   @returns Boolean
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function endsWith(p_string:String, p_end:String):Boolean {
            return p_string.lastIndexOf(p_end) == p_string.length - p_end.length;
        }

        /**
        *   Determines whether the specified string contains text.
        *
        *   @param p_string The string to check.
        *
        *   @returns Boolean
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function hasText(p_string:String):Boolean {
            var str:String = removeExtraWhitespace(p_string);
            return !!str.length;
        }

        /**
        *   Determines whether the specified string contains any characters.
        *
        *   @param p_string The string to check
        *
        *   @returns Boolean
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function isEmpty(p_string:String):Boolean {
            if (p_string == null) { return true; }
            return !p_string.length;
        }

        /**
        *   Determines whether the specified string is numeric.
        *
        *   @param p_string The string.
        *
        *   @returns Boolean
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function isNumeric(p_string:String):Boolean {
            if (p_string == null) { return false; }
            var regx:RegExp = /^[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$/;
            return regx.test(p_string);
        }

        /**
        * Pads p_string with specified character to a specified length from the left.
        *
        *   @param p_string String to pad
        *
        *   @param p_padChar Character for pad.
        *
        *   @param p_length Length to pad to.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function padLeft(p_string:String, p_padChar:String, p_length:uint):String {
            var s:String = p_string;
            while (s.length < p_length) { s = p_padChar + s; }
            return s;
        }

        /**
        * Pads p_string with specified character to a specified length from the right.
        *
        *   @param p_string String to pad
        *
        *   @param p_padChar Character for pad.
        *
        *   @param p_length Length to pad to.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function padRight(p_string:String, p_padChar:String, p_length:uint):String {
            var s:String = p_string;
            while (s.length < p_length) { s += p_padChar; }
            return s;
        }

        /**
        *   Properly cases' the string in "sentence format".
        *
        *   @param p_string The string to check
        *
        *   @returns String.
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function properCase(p_string:String):String {
            if (p_string == null) { return ''; }
            var str:String = p_string.toLowerCase().replace(/\b([^.?;!]+)/, capitalize);
            return str.replace(/\b[i]\b/, "I");
        }

        /**
        *   Escapes all of the characters in a string to create a friendly "quotable" sting
        *
        *   @param p_string The string that will be checked for instances of remove
        *   string
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function quote(p_string:String):String {
            var regx:RegExp = /[\\"\r\n]/g;
            return '"'+ p_string.replace(regx, _quote) +'"'; //"
        }

        /**
        *   Removes all instances of the remove string in the input string.
        *
        *   @param p_string The string that will be checked for instances of remove
        *   string
        *
        *   @param p_remove The string that will be removed from the input string.
        *
        *   @param p_caseSensitive An optional boolean indicating if the replace is case sensitive. Default is true.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function remove(p_string:String, p_remove:String, p_caseSensitive:Boolean = true):String {
            if (p_string == null) { return ''; }
            var rem:String = escapePattern(p_remove);
            var flags:String = (!p_caseSensitive) ? 'ig' : 'g';
            return p_string.replace(new RegExp(rem, flags), '');
        }

        /**
        *   Removes extraneous whitespace (extra spaces, tabs, line breaks, etc) from the
        *   specified string.
        *
        *   @param p_string The String whose extraneous whitespace will be removed.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function removeExtraWhitespace(p_string:String):String {
            if (p_string == null) { return ''; }
            var str:String = trim(p_string);
            return str.replace(/\s+/g, ' ');
        }

        /**
        *   Returns the specified string in reverse character order.
        *
        *   @param p_string The String that will be reversed.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function reverse(p_string:String):String {
            if (p_string == null) { return ''; }
            return p_string.split('').reverse().join('');
        }

        /**
        *   Returns the specified string in reverse word order.
        *
        *   @param p_string The String that will be reversed.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function reverseWords(p_string:String):String {
            if (p_string == null) { return ''; }
            return p_string.split(/\s+/).reverse().join('');
        }

        /**
        *   Determines the percentage of similiarity, based on editDistance
        *
        *   @param p_source The source string.
        *
        *   @param p_target The target string.
        *
        *   @returns Number
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function similarity(p_source:String, p_target:String):Number {
            var ed:uint = editDistance(p_source, p_target);
            var maxLen:uint = Math.max(p_source.length, p_target.length);
            if (maxLen == 0) { return 100; }
            else { return (1 - ed/maxLen) * 100; }
        }

        /**
        *   Remove's all < and > based tags from a string
        *
        *   @param p_string The source string.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function stripTags(p_string:String):String {
            if (p_string == null) { return ''; }
            return p_string.replace(/<\/?[^>]+>/igm, '');
        }

        /**
        *   Swaps the casing of a string.
        *
        *   @param p_string The source string.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function swapCase(p_string:String):String {
            if (p_string == null) { return ''; }
            return p_string.replace(/(\w)/, _swapCase);
        }

        /**
        *   Removes whitespace from the front and the end of the specified
        *   string.
        *
        *   @param p_string The String whose beginning and ending whitespace will
        *   will be removed.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function trim(p_string:String):String {
            if (p_string == null) { return ''; }
            return p_string.replace(/^\s+|\s+$/g, '');
        }

        /**
        *   Removes whitespace from the front (left-side) of the specified string.
        *
        *   @param p_string The String whose beginning whitespace will be removed.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function trimLeft(p_string:String):String {
            if (p_string == null) { return ''; }
            return p_string.replace(/^\s+/, '');
        }

        /**
        *   Removes whitespace from the end (right-side) of the specified string.
        *
        *   @param p_string The String whose ending whitespace will be removed.
        *
        *   @returns String .
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function trimRight(p_string:String):String {
            if (p_string == null) { return ''; }
            return p_string.replace(/\s+$/, '');
        }

        /**
        *   Determins the number of words in a string.
        *
        *   @param p_string The string.
        *
        *   @returns uint
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function wordCount(p_string:String):uint {
            if (p_string == null) { return 0; }
            return p_string.match(/\b\w+\b/g).length;
        }

        /**
        *   Returns a string truncated to a specified length with optional suffix
        *
        *   @param p_string The string.
        *
        *   @param p_len The length the string should be shortend to
        *
        *   @param p_suffix (optional, default=...) The string to append to the end of the truncated string.
        *
        *   @returns String
        *
        *   @langversion ActionScript 3.0
        *   @playerversion Flash 9.0
        *   @tiptext
        */
        public static function truncate(p_string:String, p_len:uint, p_suffix:String = "..."):String {
            if (p_string == null) { return ''; }
            p_len -= p_suffix.length;
            var trunc:String = p_string;
            if (trunc.length > p_len) {
                trunc = trunc.substr(0, p_len);
                if (/[^\s]/.test(p_string.charAt(p_len))) {
                    trunc = trimRight(trunc.replace(/\w+$|\s+$/, ''));
                }
                trunc += p_suffix;
            }

            return trunc;
        }

        /* **************************************************************** */
        /*  These are helper methods used by some of the above methods.     */
        /* **************************************************************** */
        private static function escapePattern(p_pattern:String):String {
            // RM: might expose this one, I've used it a few times already.
            return p_pattern.replace(/(\]|\[|\{|\}|\(|\)|\*|\+|\?|\.|\\)/g, '\\$1');
        }

        private static function _minimum(a:uint, b:uint, c:uint):uint {
            return Math.min(a, Math.min(b, Math.min(c,a)));
        }

        private static function _quote(p_string:String, ...args):String {
            switch (p_string) {
                case "\\":
                    return "\\\\";
                case "\r":
                    return "\\r";
                case "\n":
                    return "\\n";
                case '"':
                    return '\\"';
                default:
                    return '';
            }
        }

        private static function _upperCase(p_char:String, ...args):String {
            trace('cap latter ',p_char)
            return p_char.toUpperCase();
        }

        private static function _swapCase(p_char:String, ...args):String {
            var lowChar:String = p_char.toLowerCase();
            var upChar:String = p_char.toUpperCase();
            switch (p_char) {
                case lowChar:
                    return upChar;
                case upChar:
                    return lowChar;
                default:
                    return p_char;
            }
        }

    }
}